package com.iocup.keybastion.sso.core;


import com.ejlchina.okhttps.HTTP;
import com.ejlchina.okhttps.HttpResult;
import com.ejlchina.okhttps.OkHttps;
import com.iocup.keybastion.context.SecurityContextHolder;
import com.iocup.keybastion.context.WebContextHolder;
import com.iocup.keybastion.core.session.Session;
import com.iocup.keybastion.exception.AuthenticationException;
import com.iocup.keybastion.sso.ClientService;
import com.iocup.keybastion.sso.ClientStoreService;
import com.iocup.keybastion.sso.ServerSsoAuthLogic;
import com.iocup.keybastion.sso.TicketService;
import com.iocup.keybastion.sso.common.*;
import com.iocup.keybastion.sso.config.Client;
import com.iocup.keybastion.sso.config.SsoApiProperties;
import com.iocup.keybastion.sso.config.SsoProperties;
import com.iocup.keybastion.utils.AuthUtils;
import com.pine.sunflower.core.id.IDGenerator;
import com.pine.sunflower.core.model.HttpStatus;
import com.pine.sunflower.core.model.Result;
import com.pine.sunflower.core.utils.JsonUtils;
import com.pine.sunflower.core.utils.Strings;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * @author xyjxust
 * @create 2023/1/18 17:37
 **/
@Slf4j
public class ServerSsoAuthLogicImpl implements ServerSsoAuthLogic {

    private SsoProperties ssoProperties;
    private TicketService ticketService;
    /**
     * 不是前后端分离模式时，需要跳转的视图地址
     */
    @Setter
    private LoginViewFunction loginViewFunction = contextHolder -> "login.html";

    private SsoApiProperties ssoApiProperties;


    private ClientStoreService clientStoreService;

    @Setter
    @Getter
    private HTTP http;
    @Setter
    private ClientService clientService;


    public ServerSsoAuthLogicImpl(SsoProperties ssoProperties,
                                  TicketService ticketService,
                                  SsoApiProperties ssoApiProperties,
                                  ClientStoreService clientStoreService) {
        this.ssoProperties = ssoProperties;
        this.ticketService = ticketService;
        this.ssoApiProperties = ssoApiProperties;
        this.clientStoreService = clientStoreService;

    }

    @Override
    public Object handle() {
        WebContextHolder webContextHolder = SecurityContextHolder.getContext().getWebContextHolder();
        String uri = webContextHolder.getRequestURL();
        String method = webContextHolder.getRequestMethod();
        if (uri.equals(ssoApiProperties.ssoAuth)&& "GET".equalsIgnoreCase(method)) {
            return serverSsoAuth(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoLogin) && "POST".equalsIgnoreCase(method)) {
            return serverSsoLogin(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoUserinfo) && "GET".equalsIgnoreCase(method)) {
            return serverSsoUserinfo(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoCheckTicket) && "POST".equalsIgnoreCase(method)) {
            return serverSsoCheckTicket(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoLogout) && "POST".equalsIgnoreCase(method)) {
            return serverSsoLogout(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoGetRedirect) && "GET".equalsIgnoreCase(method)) {
            return serverSsoGetRedirect(webContextHolder);
        }
        throw new SsoRouterNotFoundException(HttpStatus.METHOD_NOT_ALLOWED.getDescribe());
    }

    private Object serverSsoAuth(WebContextHolder webContextHolder) {
        log.debug("=== SSO SERVER LOGIC ===");
        if (!AuthUtils.isLogin() && !ssoProperties.isFrontSep()) {
            return this.loginViewFunction.handle(webContextHolder);
        }
        RequestParam requestParam = SsoUtils.parseRequestParam(webContextHolder);
        if (StringUtils.isBlank(requestParam.getState())) {
            throw new AuthenticationException("未找到state");
        }
        if (StringUtils.isBlank(requestParam.getCallback())) {
            throw new AuthenticationException("未找到back");
        }
        if (StringUtils.isBlank(requestParam.getChallengeMethod())) {
            throw new AuthenticationException("未找到code_challenge_method");
        }
        if (StringUtils.isBlank(requestParam.getChallenge())) {
            throw new AuthenticationException("未找到code_challenge");
        }
        if (StringUtils.isBlank(requestParam.getClientId())) {
            throw new AuthenticationException("未找到client_id");
        }
        if (!AuthUtils.isLogin()) {
            webContextHolder.responseRedirect(SsoUtils.buildAuthRedirectUrl(ssoProperties.getLoginUrl(), requestParam.getCallback(),
                    requestParam.getState(), requestParam.getChallengeMethod(), requestParam.getChallenge(), requestParam.getClientId()));
            return null;
        }
        this.clientStoreService.saveClientId(requestParam.getClientId(),AuthUtils.getLoginName());
        webContextHolder.responseRedirect(buildCallbackUrl(requestParam.getCallback(),
                requestParam.getChallengeMethod(), requestParam.getChallenge(), requestParam.getState(), requestParam.getClientId()));
        return null;
    }

    private Object serverSsoLogin(WebContextHolder webContextHolder) {
        AuthUtils.login();
        return Result.success(AuthUtils.getToken());
    }

    private Object serverSsoCheckTicket(WebContextHolder webContextHolder) {
        Map<String, String> params = webContextHolder.getRequestBody();
        if (params == null || params.size() == 0) {
            throw new IllegalArgumentException("未传输认证参数");
        }
        String ticket = Optional.ofNullable(params.get(SsoConstant.ticket)).orElseThrow(() -> new IllegalArgumentException("ticket不存在"));
        String code_verifier = Optional.ofNullable(params.get(SsoConstant.code_verifier)).orElseThrow(() -> new IllegalArgumentException("code_verifier不存在"));
        try {
            TicketRequestParam ticketRequestParam = ticketService.getByTicket(ticket);
            if (ticketRequestParam == null) {
                throw new AuthenticationException("ticket已失效");
            }
            String challenge = ChallengeDigestsUtils.digest(code_verifier,
                    ticketRequestParam.getChallengeMethod());
            if (!challenge.equals(ticketRequestParam.getChallenge())) {
                throw new AuthenticationException("非法的code_verifier");
            }
            Optional<String> optionalClient = webContextHolder.getRequestHeader("Authorization");
            if (!optionalClient.isPresent()) {
                throw new AuthenticationException("客户端信息不存在");
            }
            //检查客户端的id和密钥信息
            String[] clientInfo = SsoUtils.decodeClientAuthenticationHeader(optionalClient.get());
            if(clientInfo==null){
                throw new AuthenticationException("客户端信息不存在");
            }
            checkClientInfo(clientInfo[0], clientInfo[1]);
            Optional<Session> optionalSession = AuthUtils.getSessionById(ticketRequestParam.getSessionId());
            if (!optionalSession.isPresent()) {
                throw new AuthenticationException("登录已失效，请重新登录");
            }
            return Result.success(optionalSession.get().getUserProfile());
        } finally {
            ticketService.removeTicket(ticket);
        }
    }

    private Object serverSsoUserinfo(WebContextHolder webContextHolder) {
        return Result.success(AuthUtils.getUserInfo());
    }

    private Object serverSsoGetRedirect(WebContextHolder webContextHolder) {
        // 未登录情况下，返回 code=401
        if (!AuthUtils.isLogin()) {
            return false;
        }
        Map<String, Object> map = new HashMap<>();
        map.put("token", AuthUtils.getToken());
        Optional<String> optionalCall = webContextHolder.getRequestParameter(SsoConstant.back);
        Optional<String> optionalState = webContextHolder.getRequestParameter(SsoConstant.state);
        Optional<String> optionalChallengeMethod = webContextHolder.getRequestParameter(SsoConstant.code_challenge_method);
        Optional<String> optionalChallenge = webContextHolder.getRequestParameter(SsoConstant.code_challenge);
        Optional<String> optionalClient = webContextHolder.getRequestParameter(SsoConstant.client_id);
        if (!optionalCall.isPresent() || !optionalState.isPresent() || !optionalChallengeMethod.isPresent()
                || !optionalChallenge.isPresent()) {
            Optional<String> optionalRedirect = webContextHolder.getRequestParameter(SsoConstant.redirect);
            optionalRedirect.ifPresent(s -> map.put("redirect", s));
            return Result.success(map);
        }
        this.clientStoreService.saveClientId(optionalClient.get(),AuthUtils.getLoginName());
        map.put("redirect", buildCallbackUrl(optionalCall.get(),
                optionalChallengeMethod.get(), optionalChallenge.get(), optionalState.get(), optionalClient.get()));
        return Result.success(map);
    }

    private Object serverSsoLogout(WebContextHolder webContextHolder) {
        Map<String, String> params = webContextHolder.getRequestBody();
        if (params == null ) {
            params = new HashMap<>();
        }
        String loginName =params.get(SsoConstant.loginId);
        if (StringUtils.isNotBlank(loginName)) {
            Optional<String> optionalClient = webContextHolder.getRequestHeader("Authorization");
            if (!optionalClient.isPresent()) {
                throw new AuthenticationException("客户端信息不存在");
            }
            //检查客户端的id和密钥信息
            String[] clientInfo = SsoUtils.decodeClientAuthenticationHeader(optionalClient.get());
            if(clientInfo==null){
                throw new AuthenticationException("客户端信息不存在");
            }
            checkClientInfo(clientInfo[0], clientInfo[1]);
        } else {
            if (!AuthUtils.isLogin()) {
                throw new AuthenticationException("登录凭证错误");
            }
            loginName = AuthUtils.getLoginName();
        }
        if (StringUtils.isNotBlank(loginName)) {
            Set<String> clients = clientStoreService.getByClientId(loginName);
            if(clients!=null){
                for (String clientId : clients) {
                    Client client = clientService.get(clientId);
                    if (client != null) {
                        httpClientLogout(client);
                    }
                }
                this.clientStoreService.removeClientId(loginName);
            }

            AuthUtils.logout(loginName, "", "");
        }
        return null;
    }

    private void httpClientLogout(Client client) {
        HttpResult httpResult = getHttp().sync(client.getLogoutUrl())
                .addHeader("Content-Type", "application/json")
                .bodyType(OkHttps.JSON)
                .addBodyPara(SsoConstant.loginId, AuthUtils.getLoginName())
                .addHeader("Authorization", SsoUtils.getAuthorizationHeader(client.getClientId(), client.getClientSecret()))
                .post();
        if (!httpResult.isSuccessful()) {
            log.error(String.format("登出客户端[{}]失败，原因：%s", client.getClientId(), httpResult.getBody().toString()));
        }
        Result<String> result = JsonUtils.toObject(httpResult.getBody().toString(), Result.class);
        if (!result.isSuccess()) {
            log.error(String.format("登出客户端[{}]失败，原因：%s", client.getClientId(), result.getMessage()));
        }
    }

    public String buildCallbackUrl(String call, String challengeMethod, String challenge, String state, String clientId) {
        checkCallUrl(clientId, call);
        String ticket = buildTicket(challengeMethod, challenge, clientId);
        return SsoUtils.buildCallbackUrl(call, ticket, state);
    }

    private String buildTicket(String challengeMethod,
                               String challenge, String clientId) {
        Optional<Session> optionalSession = AuthUtils.getSession(AuthUtils.getToken().getAccessToken());
        if (!optionalSession.isPresent()) {
            throw new AuthenticationException("登录已失效，请重新登录");
        }
        String ticket = this.ticketService.getTicketBySessionId(optionalSession.get().getId());
        if (StringUtils.isNotBlank(ticket)) {
            //移除以前的票据
            this.ticketService.removeTicket(this.ticketService.getTicketBySessionId(optionalSession.get().getId()));
        }

        ticket = IDGenerator.UUID.generate();
        TicketRequestParam ticketRequestParam = new TicketRequestParam();
        ticketRequestParam.setTicket(ticket);
        ticketRequestParam.setSessionId(optionalSession.get().getId());
        ticketRequestParam.setChallenge(challenge);
        ticketRequestParam.setChallengeMethod(challengeMethod);
        ticketRequestParam.setClientId(clientId);
        //重新生成新的票据
        ticketService.saveTicket(ticket, ticketRequestParam);
        return ticket;
    }

    private void checkCallUrl(String clientId, String call) {
        Client client = clientService.get(clientId);
        if (client == null) {
            throw new AuthenticationException(Strings.format("客户端[{}]不存在", clientId));
        }
        if (!client.getCallbackUrl().equals(call)) {
            throw new AuthenticationException(Strings.format("客户端[{}]的回调地址与认证服务配置不一致", clientId));
        }
    }

    private void checkClientInfo(String clientId, String clientSecret) {
        Client client = clientService.get(clientId);
        if (client == null) {
            throw new AuthenticationException(Strings.format("客户端[{}]不存在", clientId));
        }
        if (!client.getClientSecret().equals(clientSecret)) {
            throw new AuthenticationException(Strings.format("客户端[{}]的密钥错误", clientId));
        }
    }
}
